﻿// SyntheticDataGenerator.Core.Transaction
//
// (c) 2011 Arthur Pitman
//
// Part of Market-Basket Synthetic Data Generator
// A C# adaptation of the IBM Quest Market-Basket Synthetic Data Generator
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// (Version 2) as published by the Free Software Foundation.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License (Version 2) along with this program; if not, write to 
// the Free Software Foundation, Inc., 
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// or see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SyntheticDataGenerator.Generators;
using System.IO;

namespace SyntheticDataGenerator.Core
{
	/// <summary>
	/// Represents a transaction
	/// </summary>
	public class Transaction
	{   
		/// <summary>
		/// Source of randomness
		/// </summary>
		protected static UniformGenerator uniformGenerator = new UniformGenerator();

		/// <summary>
		/// Counter for allocating <see cref="Transaction"/> ids
		/// </summary>
		protected static int nextId = 0;

		/// <summary>
		/// The id of the <see cref="Transaction"/>
		/// </summary>
		protected int id;

		/// <summary>
		/// The expected number of items in the <see cref="Transaction"/>
		/// </summary>
		protected int expectedLength;

		/// <summary>
		/// The items in the <see cref="Transaction"/>
		/// </summary>
		public List<int> Items = new List<int>();

		/// <summary>
		/// Initializes a new <see cref="Transaction"/> instance with the specified expected transaction length
		/// </summary>
		/// <param name="expectedLength"></param>
		public Transaction(int expectedLength)
		{
			this.expectedLength = expectedLength;
			id = nextId;
			nextId++;
		}

		/// <summary>
		/// Initializes a new <see cref="Transaction"/> instance sourced from the specified <see cref="PatternSetIterator"/> and with the given expected transaction length
		/// </summary>
		/// <param name="lits"></param>
		/// <param name="tlen"></param>
		public Transaction(PatternSetIterator lits, int tlen) : this(tlen)
		{                        
			while (Items.Count < tlen)
			{
				Pattern pat = lits.GetPattern();

				// if the pattern doesn't fit in the transaction, put it back and stop
				if (!AddPattern(pat))
				{                    
					lits.UnGetPattern();
					break;
				}
			}            
		}

		/// <summary>
		/// Adds a <see cref="Pattern"/> to the <see cref="Transaction"/>, optionally corrupting it
		/// </summary>
		/// <param name="pattern"></param>
		/// <param name="corrupt"></param>
		/// <returns><c>true</c> if the <see cref="Pattern"/> fitted, otherwise<c>false</c></returns>
		public bool AddPattern(Pattern pattern, bool corrupt = true)
		{
			int patternLength = pattern.Items.Length;
			if (corrupt)
			{
				while (patternLength > 0 && uniformGenerator.Next() > pattern.Confidence)
				{
					patternLength--;
				}
			}

			// in half of the cases, drop the pattern that won't fit
			if ((patternLength + Items.Count > expectedLength) && (uniformGenerator.Next() > 0.5))
					return false;

			// pick "patlen" items at random from pattern           
			var shuffleIndexes = uniformGenerator.ChooseRandomSubset(pattern.Items.Length, patternLength);
			foreach(var itemIndex in shuffleIndexes)
				Items.Add(pattern.Items[itemIndex]);
		   
			return true;
		}

		/// <summary>
		/// Writes the <see cref="Transaction"/> to the specified <see cref="TextWriter"/>
		/// </summary>
		/// <param name="writer"></param>
		public void Write(TextWriter writer)
		{
			foreach (var item in Items)
				writer.WriteLine("{0} {1}", id, item);
		}
		
		/// <summary>
		/// Writes the <see cref="Transaction"/> to the specified <see cref="TextWriter"/>
		/// </summary>
		/// <param name="writer"></param>
		/// <param name="sequenceId"></param>
		public void Write(TextWriter writer, int sequenceId)
		{
			foreach (var item in Items)
				writer.WriteLine("{0} {1} {2}", sequenceId, id, item);
		}
	}
}
